#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include <X11/Xlib.h>
#include <X11/Xlib-xcb.h>
#include <xcb/xcb.h>

#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>

#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, const int *);

static bool isExtensionSupported(const char *extensions, const char *extension)
{
    const char *start;
    const char *where, *terminator;

    /* Extension names should not have spaces. */
    where = strchr(extension, ' ');
    if (where || *extension == '\0')
    {
        return false;
    }

    /* It takes a bit of care to be fool-proof about parsing the
       OpenGL extensions string. Don't be fooled by sub-strings,
       etc. */
    start = extensions;
    for (;;)
    {
        where = strstr(start, extension);
        if (!where)
        {
            break;
        }
        terminator = where + strlen(extension);
        if (where == start || *(where - 1) == ' ')
        {
            if (*terminator == ' ' || *terminator == '\0')
            {
                return true;
            }
        }
        start = terminator;
    }
    return false;
}

static bool ctxErrorOccurred = false;
static int ctxErrorHandler(Display *dpy, XErrorEvent *ev)
{
    ctxErrorOccurred = true;
    return 0;
}

void DrawQuad()
{
    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1., 1., -1., 1., 1., 20.);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

    glBegin(GL_QUADS);

    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(-0.75, -0.75, 0.0);

    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(0.75, -0.75, 0.0);




    glColor3f(0.0, 0.0, 1.0);
    glVertex3f(0.75, 0.75, 0.0);

    glColor3f(1.0, 1.0, 0.0);
    glVertex3f(-0.75, 0.75, 0.0);

    glEnd();
}

int main()
{
    static int visual_attribs[] = {
        GLX_X_RENDERABLE, True,
        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
        GLX_RED_SIZE, 8,
        GLX_GREEN_SIZE, 8,
        GLX_BLUE_SIZE, 8,
        GLX_ALPHA_SIZE, 8,
        GLX_DEPTH_SIZE, 24,
        GLX_STENCIL_SIZE, 8,
        GLX_DOUBLEBUFFER, True,
        // GLX_SAMPLE_BUFFERS, 1
        // GLX_SAMPLES,        4,
        None

    };

    // Open Xlib Display
    // 获取当前显示设备
    Display *display = XOpenDisplay(NULL);
    if (!display)
    {
        fprintf(stderr, "Can't open display!\n");
        return -1;
    }

    // FBConfigs were added in GLX version 1.3,
    int glx_major;
    int glx_minor;
    if (!glXQueryVersion(display, &glx_major, &glx_minor) ||
        (glx_major == 1 && glx_minor < 3) ||
        (glx_major < 1))
    {
        fprintf(stderr, "Invalid GLX version\n");
        return -1;
    }

    printf("GLX Version %d.%d\n", glx_major, glx_minor);

    int default_screen = DefaultScreen(display);

    // Query framebuffer configurations
    int num_fb_configs = 0;
    GLXFBConfig fb_config;
    GLXFBConfig *fb_configs = glXChooseFBConfig(display, default_screen, visual_attribs, &num_fb_configs);
    if (!fb_configs || num_fb_configs == 0)
    {
        fprintf(stderr, "glxGetFBConfigs failed!\n");
        return -1;
    }

    // Pick the FB config/visual with the most samples per pixel
    {
        int best_fbc = -1;
        int worst_fbc = -1;
        int best_num_sample = -1;
        int worst_num_sample = 999;

        for (int i = 0; i < num_fb_configs; i++)
        {
            XVisualInfo *vi = glXGetVisualFromFBConfig(display, fb_configs[i]);
            if (vi)
            {
                int samp_buf;
                int samples;

                glXGetFBConfigAttrib(display, fb_configs[i], GLX_SAMPLE_BUFFERS, &samp_buf);
                glXGetFBConfigAttrib(display, fb_configs[i], GLX_SAMPLES, &samples);

                printf("  Match fbconfig %d, visual ID 0x%ld: SAMPLE_BUFFERS = %d, SAMPLES = %d\n",
                       i, vi->visualid, samp_buf, samples);

                if (best_fbc < 0 || (samp_buf && samples > best_num_sample))
                {
                    best_fbc = i;
                    best_num_sample = samples;
                }
                if (worst_fbc < 0 || !samp_buf || samples < worst_num_sample)
                {
                    worst_fbc = i;
                    worst_num_sample = samples;
                }
            }
            XFree(vi);
        }

        fb_config = fb_configs[best_fbc];
    }

    XVisualInfo *vi = glXGetVisualFromFBConfig(display, fb_config);
    printf("Choose visualID = 0x%lx\n", vi->visualid);

    /* Open the connection to the X server */
    xcb_connection_t *connection = XGetXCBConnection(display);
    if (!connection)
    {
        XCloseDisplay(display);
        fprintf(stderr, "Can't get xcb connection from display\n");
        return -1;
    }

    XSetEventQueueOwner(display, XCBOwnsEventQueue);

    /* Get XCB screen */
    const xcb_setup_t *setup = xcb_get_setup(connection);
    xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
    for (int screen_num = vi->screen; iter.rem && screen_num > 0; --screen_num, xcb_screen_next(&iter))
        ;

    xcb_screen_t *screen = iter.data;

    // get the root window
    xcb_window_t window = screen->root;

    xcb_colormap_t colormap = xcb_generate_id(connection);

    xcb_create_colormap(
        connection,
        XCB_COLORMAP_ALLOC_NONE,
        colormap,
        window,
        vi->visualid);

    /* Create the window */
    window = xcb_generate_id(connection);
    uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t values[] = {
        XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS,
        colormap,
        0};

    xcb_create_window(connection,                    /* Connection          */
                      XCB_COPY_FROM_PARENT,          /* depth (same as root)*/
                      window,                        /* window Id           */
                      screen->root,                  /* parent window       */
                      20, 20,                        /* x, y                */
                      640, 480,                      /* width, height       */
                      10,                            /* border_width        */
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class               */
                      vi->visualid,                  /* visual              */
                      mask,                          /* masks               */
                      values);

    XFree(vi);

    const char *title = "Hello, Engine![OpenGL]";
    const char *title_icon = "Hello, Engine! (iconified)";

    xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window,
                        XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
                        strlen(title), title);

    xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window,
                        XCB_ATOM_WM_ICON_NAME, XCB_ATOM_STRING, 8,
                        strlen(title_icon), title_icon);

    /* Map the window on the screen */
    xcb_map_window(connection, window);

    /* Make sure commands are sent before we pause so that the window gets shown */
    xcb_flush(connection);

    // get the default screen's GLX extension list
    const char *glxExts = glXQueryExtensionsString(display, default_screen);

    // Note: it is not necessary to create or make current to a context before
    // calling glXGetProcAddressARB
    glXCreateContextAttribsARBProc glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");

    // create OpenGL context
    ctxErrorOccurred = false;
    int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&ctxErrorHandler);

    GLXContext context;
    if (!isExtensionSupported(glxExts, "GLX_ARB_creat_context") || !glXCreateContextAttribsARB)
    {
        printf("glXCreateContextAttribsARB() not found ... using old-style GLX context!\n");
        context = glXCreateNewContext(display, fb_config, GLX_RGBA_TYPE, 0, True);
        if (!context)
        {
            fprintf(stderr, "glXCreateNewContext failed!\n");
            return -1;
        }
    }
    else
    {
        int context_attribs[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
            GLX_CONTEXT_MINOR_VERSION_ARB, 0,
            None};

        printf("Creating context!\n");
        context = glXCreateContextAttribsARB(display, fb_config, 0, True, context_attribs);

        XSync(display, False);
        if (!ctxErrorOccurred && context)
        {
            printf("Create GL 3.0 context!\n");
        }
        else
        {
            int context_attribs[] = {
                GLX_CONTEXT_MAJOR_VERSION_ARB, 1,
                GLX_CONTEXT_MINOR_VERSION_ARB, 0,
                None};
            ctxErrorOccurred = false;
            printf("  Failed to create GL 3.0 context ... Using old-style GLX context!\n");
            context = glXCreateContextAttribsARB(display, fb_config, 0, True, context_attribs);
        }
    }

    XSync(display, False);
    XSetErrorHandler(oldHandler);

    if (ctxErrorOccurred || !context){
        printf("Failed to create an OpenGl context\n");
        return -1;
    }

    // Verifying that context is a direct context
    if (!glXIsDirect(display, context)) {
        printf("Indirect GLX rendering context obtained\n");
    } else {
        printf("Direct GLX rendering context obtained\n");
    }

    // create GLX window
    GLXWindow glxwindow = glXCreateWindow(
        display,
        fb_config,
        window,
        0);

    if (!glxwindow) {
        xcb_destroy_window(connection, window);
        glXDestroyContext(display, context);
        
        fprintf(stderr, "glXCreateWindow failed\n");
        return -1;
    }

    printf("create glxwindow success!");

    GLXDrawable drawable = glxwindow;
    // make OpenGL context current;
    if (!glXMakeContextCurrent(display, drawable, drawable, context)) {
        xcb_destroy_window(connection, window);
        glXDestroyContext(display, context);
        
        fprintf(stderr, "glXMakeContextCurrent failed\n");
        return -1;
    }
    uint8_t isQuit = 0;
    xcb_generic_event_t *pEvent;

    while ((pEvent = xcb_wait_for_event(connection)) && !isQuit)
    {
        switch (pEvent->response_type & ~0x80)
        {
        case XCB_EXPOSE:
            printf("XCB_EXPOSE ...........\n");
            DrawQuad();
            glXSwapBuffers(display, drawable);
            break;
        case XCB_KEY_PRESS:
            isQuit = 1;
            printf("XCB_EXPOSE XCB_KEY_PRESS ...........\n");
            break;
        }
        free(pEvent);
    }

    xcb_disconnect(connection);
    printf("end ...........");
    return 0;
}